"""Offline Configuration Manager.

Handles offline configuration caching, synchronization, conflict resolution,
and queue management for reliable configuration management in unstable network conditions.
"""

import asyncio
import hashlib
import json
import logging
import sqlite3
import time
import uuid
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Optional, Any, Callable, Tuple
from dataclasses import dataclass, field, asdict
from enum import Enum
from contextlib import asynccontextmanager

import aiofiles
import aiohttp

from .template_manager import TemplateManager
from .batch_manager import BatchConfigManager, DeviceInfo
from ..utils.storage import FileStorage
from ..utils.cache import LRUCache

logger = logging.getLogger(__name__)


class ConfigStatus(Enum):
    """Configuration status enumeration."""
    PENDING = "pending"              # Waiting to be applied
    APPLYING = "applying"            # Currently being applied
    APPLIED = "applied"              # Successfully applied
    FAILED = "failed"                # Application failed
    SYNCING = "syncing"              # Synchronizing
    SYNCED = "synced"                # Successfully synced
    CONFLICT = "conflict"            # Has conflicts


class NetworkStatus(Enum):
    """Network status enumeration."""
    ONLINE = "online"                # Online and stable
    OFFLINE = "offline"              # No network connection
    UNSTABLE = "unstable"            # Intermittent connection
    RECONNECTING = "reconnecting"    # Attempting to reconnect


class OperationType(Enum):
    """Configuration operation type."""
    CREATE = "create"                # Create new configuration
    UPDATE = "update"                # Update existing configuration
    DELETE = "delete"                # Delete configuration
    APPLY_TEMPLATE = "apply_template" # Apply template


class ConflictType(Enum):
    """Configuration conflict type."""
    VALUE = "value"                  # Value difference
    TYPE = "type"                    # Data type difference
    STRUCTURE = "structure"          # Structure difference
    DEPENDENCY = "dependency"        # Dependency conflict


@dataclass
class OfflineConfigEntry:
    """Offline configuration entry."""
    entry_id: str                    # Configuration entry ID
    device_id: str                   # Target device ID
    config_data: Dict[str, Any]      # Configuration data
    template_id: Optional[str] = None # Associated template ID
    operation_type: OperationType = OperationType.UPDATE
    
    # Status information
    status: ConfigStatus = ConfigStatus.PENDING
    priority: int = 5                # Priority (1-10, higher = more important)
    created_at: datetime = field(default_factory=datetime.now)
    updated_at: datetime = field(default_factory=datetime.now)
    scheduled_at: Optional[datetime] = None
    attempts: int = 0                # Attempt count
    last_attempt_at: Optional[datetime] = None
    
    # Synchronization information
    local_version: str = "1.0.0"     # Local version
    remote_version: Optional[str] = None # Remote version
    checksum: str = field(default="") # Data checksum
    conflict_info: Optional[Dict[str, Any]] = None
    
    # Metadata
    source: str = "local"            # Configuration source
    tags: List[str] = field(default_factory=list)
    dependencies: List[str] = field(default_factory=list)
    rollback_data: Optional[Dict[str, Any]] = None
    
    def __post_init__(self):
        """Post-initialization processing."""
        if not self.checksum:
            self.checksum = self._calculate_checksum()
    
    def _calculate_checksum(self) -> str:
        """Calculate configuration checksum."""
        content = json.dumps(self.config_data, sort_keys=True)
        return hashlib.sha256(content.encode('utf-8')).hexdigest()


@dataclass
class SyncOperation:
    """Synchronization operation record."""
    operation_id: str                # Operation ID
    operation_type: str              # Sync type (FULL, INCREMENTAL)
    device_ids: List[str]            # Synchronized device list
    start_time: datetime             # Start time
    end_time: Optional[datetime] = None # End time
    status: str = "running"          # Sync status
    
    # Statistics
    total_configs: int = 0           # Total configurations
    synced_configs: int = 0          # Synced configurations
    failed_configs: int = 0          # Failed configurations
    conflicts_resolved: int = 0      # Resolved conflicts
    data_transferred: int = 0        # Data transferred (bytes)
    
    # Results
    errors: List[str] = field(default_factory=list)
    warnings: List[str] = field(default_factory=list)
    resolution_log: List[Dict[str, Any]] = field(default_factory=list)


@dataclass
class ConflictEntry:
    """Configuration conflict entry."""
    conflict_id: str                 # Conflict ID
    device_id: str                   # Device ID
    field_path: str                  # Conflict field path
    
    # Conflict data
    local_value: Any                 # Local value
    remote_value: Any                # Remote value
    local_timestamp: datetime        # Local timestamp
    remote_timestamp: datetime       # Remote timestamp
    
    # Resolution information
    resolution_strategy: Optional[str] = None # Resolution strategy
    resolved_value: Optional[Any] = None # Resolved value
    resolution_timestamp: Optional[datetime] = None
    resolver: Optional[str] = None   # Resolver (auto, user)
    
    # Context information
    conflict_type: ConflictType = ConflictType.VALUE
    severity: str = "medium"         # Severity (low, medium, high)
    description: str = ""            # Conflict description


class OfflineStorage:
    """Offline configuration storage."""
    
    def __init__(self, base_path: str):
        """Initialize offline storage.
        
        Args:
            base_path: Base storage path
        """
        self.base_path = Path(base_path)
        self.db_path = self.base_path / "offline.db"
        
        # Create directory structure
        self._create_directory_structure()
        
        # Initialize database
        self._init_database()
        
        logger.info(f"OfflineStorage initialized at {base_path}")
    
    def _create_directory_structure(self):
        """Create directory structure."""
        directories = [
            "queue/pending",
            "queue/processing", 
            "queue/completed",
            "queue/failed",
            "cache/devices",
            "cache/templates",
            "cache/metadata",
            "sync/history",
            "sync/locks",
            "sync/checkpoints",
            "conflicts/unresolved",
            "conflicts/resolved",
            "conflicts/auto",
            "backup",
            "logs"
        ]
        
        for dir_path in directories:
            (self.base_path / dir_path).mkdir(parents=True, exist_ok=True)
    
    def _init_database(self):
        """Initialize SQLite database."""
        with sqlite3.connect(str(self.db_path)) as conn:
            conn.executescript("""
                CREATE TABLE IF NOT EXISTS config_entries (
                    entry_id TEXT PRIMARY KEY,
                    device_id TEXT NOT NULL,
                    config_data TEXT NOT NULL,
                    template_id TEXT,
                    operation_type TEXT NOT NULL,
                    status TEXT NOT NULL,
                    priority INTEGER DEFAULT 5,
                    created_at TIMESTAMP NOT NULL,
                    updated_at TIMESTAMP NOT NULL,
                    scheduled_at TIMESTAMP,
                    attempts INTEGER DEFAULT 0,
                    last_attempt_at TIMESTAMP,
                    local_version TEXT NOT NULL,
                    remote_version TEXT,
                    checksum TEXT NOT NULL,
                    conflict_info TEXT,
                    source TEXT DEFAULT 'local',
                    tags TEXT,
                    dependencies TEXT,
                    rollback_data TEXT
                );
                
                CREATE TABLE IF NOT EXISTS sync_operations (
                    operation_id TEXT PRIMARY KEY,
                    operation_type TEXT NOT NULL,
                    device_ids TEXT NOT NULL,
                    start_time TIMESTAMP NOT NULL,
                    end_time TIMESTAMP,
                    status TEXT NOT NULL,
                    total_configs INTEGER DEFAULT 0,
                    synced_configs INTEGER DEFAULT 0,
                    failed_configs INTEGER DEFAULT 0,
                    conflicts_resolved INTEGER DEFAULT 0,
                    data_transferred INTEGER DEFAULT 0,
                    errors TEXT,
                    warnings TEXT,
                    resolution_log TEXT
                );
                
                CREATE TABLE IF NOT EXISTS conflict_entries (
                    conflict_id TEXT PRIMARY KEY,
                    device_id TEXT NOT NULL,
                    field_path TEXT NOT NULL,
                    local_value TEXT,
                    remote_value TEXT,
                    local_timestamp TIMESTAMP NOT NULL,
                    remote_timestamp TIMESTAMP NOT NULL,
                    resolution_strategy TEXT,
                    resolved_value TEXT,
                    resolution_timestamp TIMESTAMP,
                    resolver TEXT,
                    conflict_type TEXT NOT NULL,
                    severity TEXT DEFAULT 'medium',
                    description TEXT
                );
                
                CREATE INDEX IF NOT EXISTS idx_config_device_id ON config_entries(device_id);
                CREATE INDEX IF NOT EXISTS idx_config_status ON config_entries(status);
                CREATE INDEX IF NOT EXISTS idx_config_priority ON config_entries(priority);
                CREATE INDEX IF NOT EXISTS idx_sync_status ON sync_operations(status);
                CREATE INDEX IF NOT EXISTS idx_conflict_device ON conflict_entries(device_id);
            """)
    
    async def save_config_entry(self, entry: OfflineConfigEntry) -> bool:
        """Save configuration entry."""
        try:
            with sqlite3.connect(str(self.db_path)) as conn:
                conn.execute("""
                    INSERT OR REPLACE INTO config_entries (
                        entry_id, device_id, config_data, template_id, operation_type,
                        status, priority, created_at, updated_at, scheduled_at,
                        attempts, last_attempt_at, local_version, remote_version,
                        checksum, conflict_info, source, tags, dependencies, rollback_data
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """, (
                    entry.entry_id, entry.device_id, json.dumps(entry.config_data),
                    entry.template_id, entry.operation_type.value, entry.status.value,
                    entry.priority, entry.created_at, entry.updated_at, entry.scheduled_at,
                    entry.attempts, entry.last_attempt_at, entry.local_version,
                    entry.remote_version, entry.checksum, json.dumps(entry.conflict_info),
                    entry.source, json.dumps(entry.tags), json.dumps(entry.dependencies),
                    json.dumps(entry.rollback_data)
                ))
            return True
        except Exception as e:
            logger.error(f"Error saving config entry: {e}")
            return False
    
    async def get_config_entry(self, entry_id: str) -> Optional[OfflineConfigEntry]:
        """Get configuration entry by ID."""
        try:
            with sqlite3.connect(str(self.db_path)) as conn:
                conn.row_factory = sqlite3.Row
                cursor = conn.execute(
                    "SELECT * FROM config_entries WHERE entry_id = ?",
                    (entry_id,)
                )
                row = cursor.fetchone()
                
                if row:
                    return self._row_to_config_entry(row)
                return None
        except Exception as e:
            logger.error(f"Error getting config entry: {e}")
            return None
    
    async def list_config_entries(self, device_id: Optional[str] = None,
                                 status: Optional[ConfigStatus] = None,
                                 limit: int = 100) -> List[OfflineConfigEntry]:
        """List configuration entries with filters."""
        try:
            query = "SELECT * FROM config_entries"
            params = []
            conditions = []
            
            if device_id:
                conditions.append("device_id = ?")
                params.append(device_id)
            
            if status:
                conditions.append("status = ?")
                params.append(status.value)
            
            if conditions:
                query += " WHERE " + " AND ".join(conditions)
            
            query += " ORDER BY priority DESC, created_at ASC LIMIT ?"
            params.append(limit)
            
            with sqlite3.connect(str(self.db_path)) as conn:
                conn.row_factory = sqlite3.Row
                cursor = conn.execute(query, params)
                rows = cursor.fetchall()
                
                return [self._row_to_config_entry(row) for row in rows]
        except Exception as e:
            logger.error(f"Error listing config entries: {e}")
            return []
    
    async def delete_config_entry(self, entry_id: str) -> bool:
        """Delete configuration entry."""
        try:
            with sqlite3.connect(str(self.db_path)) as conn:
                cursor = conn.execute(
                    "DELETE FROM config_entries WHERE entry_id = ?",
                    (entry_id,)
                )
                return cursor.rowcount > 0
        except Exception as e:
            logger.error(f"Error deleting config entry: {e}")
            return False
    
    def _row_to_config_entry(self, row: sqlite3.Row) -> OfflineConfigEntry:
        """Convert database row to configuration entry."""
        return OfflineConfigEntry(
            entry_id=row['entry_id'],
            device_id=row['device_id'],
            config_data=json.loads(row['config_data']),
            template_id=row['template_id'],
            operation_type=OperationType(row['operation_type']),
            status=ConfigStatus(row['status']),
            priority=row['priority'],
            created_at=datetime.fromisoformat(row['created_at']),
            updated_at=datetime.fromisoformat(row['updated_at']),
            scheduled_at=datetime.fromisoformat(row['scheduled_at']) if row['scheduled_at'] else None,
            attempts=row['attempts'],
            last_attempt_at=datetime.fromisoformat(row['last_attempt_at']) if row['last_attempt_at'] else None,
            local_version=row['local_version'],
            remote_version=row['remote_version'],
            checksum=row['checksum'],
            conflict_info=json.loads(row['conflict_info']) if row['conflict_info'] else None,
            source=row['source'],
            tags=json.loads(row['tags']) if row['tags'] else [],
            dependencies=json.loads(row['dependencies']) if row['dependencies'] else [],
            rollback_data=json.loads(row['rollback_data']) if row['rollback_data'] else None
        )


class NetworkMonitor:
    """Network status monitor."""
    
    def __init__(self, config: Optional[Dict[str, Any]] = None):
        """Initialize network monitor.
        
        Args:
            config: Monitor configuration
        """
        self.config = config or {}
        self.status = NetworkStatus.OFFLINE
        self.last_check = None
        self.check_interval = self.config.get('check_interval', 30)
        self.timeout = self.config.get('timeout', 10)
        self.retry_attempts = self.config.get('retry_attempts', 3)
        
        # Test URLs for connectivity check
        self.test_urls = self.config.get('test_urls', [
            'https://www.google.com',
            'https://1.1.1.1', 
            'https://8.8.8.8'
        ])
        
        # Status change callbacks
        self.callbacks: List[Callable[[NetworkStatus, NetworkStatus], None]] = []
        
        # Monitoring task
        self.monitor_task: Optional[asyncio.Task] = None
        
        logger.info("NetworkMonitor initialized")
    
    async def start_monitoring(self):
        """Start network status monitoring."""
        if self.monitor_task:
            return
        
        self.monitor_task = asyncio.create_task(self._monitoring_loop())
        logger.info("Network monitoring started")
    
    async def stop_monitoring(self):
        """Stop network status monitoring."""
        if self.monitor_task:
            self.monitor_task.cancel()
            try:
                await self.monitor_task
            except asyncio.CancelledError:
                pass
            self.monitor_task = None
        
        logger.info("Network monitoring stopped")
    
    async def check_connectivity(self) -> NetworkStatus:
        """Check network connectivity status."""
        successful_checks = 0
        
        for url in self.test_urls:
            for attempt in range(self.retry_attempts):
                try:
                    async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=self.timeout)) as session:
                        async with session.get(url) as response:
                            if response.status == 200:
                                successful_checks += 1
                                break
                except Exception as e:
                    logger.debug(f"Connectivity check failed for {url}: {e}")
                    await asyncio.sleep(1)  # Brief delay before retry
        
        # Determine status based on successful checks
        total_urls = len(self.test_urls)
        success_rate = successful_checks / total_urls
        
        if success_rate >= 0.8:
            return NetworkStatus.ONLINE
        elif success_rate >= 0.3:
            return NetworkStatus.UNSTABLE
        else:
            return NetworkStatus.OFFLINE
    
    def add_status_callback(self, callback: Callable[[NetworkStatus, NetworkStatus], None]):
        """Add status change callback.
        
        Args:
            callback: Callback function (old_status, new_status)
        """
        self.callbacks.append(callback)
    
    def remove_status_callback(self, callback: Callable[[NetworkStatus, NetworkStatus], None]):
        """Remove status change callback."""
        if callback in self.callbacks:
            self.callbacks.remove(callback)
    
    async def _monitoring_loop(self):
        """Network monitoring background loop."""
        while True:
            try:
                old_status = self.status
                self.status = await self.check_connectivity()
                self.last_check = datetime.now()
                
                # Notify callbacks if status changed
                if old_status != self.status:
                    self._notify_status_change(old_status, self.status)
                
                await asyncio.sleep(self.check_interval)
                
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Network monitoring error: {e}")
                await asyncio.sleep(self.check_interval)
    
    def _notify_status_change(self, old_status: NetworkStatus, new_status: NetworkStatus):
        """Notify callbacks about status change."""
        for callback in self.callbacks:
            try:
                callback(old_status, new_status)
            except Exception as e:
                logger.error(f"Status callback error: {e}")


class OfflineConfigManager:
    """Offline configuration manager."""
    
    def __init__(self, config: Dict[str, Any], 
                 template_manager: TemplateManager,
                 batch_manager: BatchConfigManager):
        """Initialize offline configuration manager.
        
        Args:
            config: Manager configuration
            template_manager: Template manager instance
            batch_manager: Batch configuration manager
        """
        self.config = config
        self.template_manager = template_manager
        self.batch_manager = batch_manager
        
        # Initialize storage
        storage_path = config.get('storage_path', '/tmp/offline_config')
        self.storage = OfflineStorage(storage_path)
        
        # Initialize network monitor
        network_config = config.get('network_monitor', {})
        self.network_monitor = NetworkMonitor(network_config)
        
        # Configuration limits
        self.max_queue_size = config.get('max_queue_size', 1000)
        self.retention_days = config.get('retention_days', 30)
        self.sync_interval = config.get('sync_interval', 60)
        
        # Processing state
        self.processing_queue = asyncio.Queue()
        self.processing_task: Optional[asyncio.Task] = None
        self.sync_task: Optional[asyncio.Task] = None
        
        # Statistics
        self.stats = {
            'queued_configs': 0,
            'processed_configs': 0,
            'failed_configs': 0,
            'sync_operations': 0,
            'conflicts_resolved': 0
        }
        
        # Setup network status callback
        self.network_monitor.add_status_callback(self._on_network_status_change)
        
        logger.info("OfflineConfigManager initialized")
    
    async def start(self):
        """Start offline configuration manager."""
        # Start network monitoring
        await self.network_monitor.start_monitoring()
        
        # Start processing queue
        self.processing_task = asyncio.create_task(self._processing_loop())
        
        # Start sync task
        self.sync_task = asyncio.create_task(self._sync_loop())
        
        logger.info("OfflineConfigManager started")
    
    async def stop(self):
        """Stop offline configuration manager."""
        # Stop tasks
        if self.processing_task:
            self.processing_task.cancel()
            try:
                await self.processing_task
            except asyncio.CancelledError:
                pass
        
        if self.sync_task:
            self.sync_task.cancel()
            try:
                await self.sync_task
            except asyncio.CancelledError:
                pass
        
        # Stop network monitoring
        await self.network_monitor.stop_monitoring()
        
        logger.info("OfflineConfigManager stopped")
    
    async def queue_config(self, device_id: str, config_data: Dict[str, Any],
                          operation_type: OperationType = OperationType.UPDATE,
                          template_id: Optional[str] = None,
                          priority: int = 5,
                          scheduled_at: Optional[datetime] = None) -> str:
        """Queue configuration for offline processing.
        
        Args:
            device_id: Target device ID
            config_data: Configuration data
            operation_type: Operation type
            template_id: Associated template ID
            priority: Priority (1-10)
            scheduled_at: Scheduled execution time
            
        Returns:
            Configuration entry ID
        """
        # Check queue size limit
        current_size = len(await self.storage.list_config_entries(
            status=ConfigStatus.PENDING
        ))
        
        if current_size >= self.max_queue_size:
            # Remove oldest low-priority entries
            await self._cleanup_queue()
        
        # Create configuration entry
        entry = OfflineConfigEntry(
            entry_id=str(uuid.uuid4()),
            device_id=device_id,
            config_data=config_data,
            template_id=template_id,
            operation_type=operation_type,
            priority=priority,
            scheduled_at=scheduled_at
        )
        
        # Save to storage
        success = await self.storage.save_config_entry(entry)
        if not success:
            raise Exception("Failed to save configuration entry")
        
        # Add to processing queue
        await self.processing_queue.put(entry.entry_id)
        
        self.stats['queued_configs'] += 1
        logger.info(f"Queued configuration: {entry.entry_id}")
        
        return entry.entry_id
    
    async def get_queue_status(self) -> Dict[str, Any]:
        """Get queue status information."""
        pending = await self.storage.list_config_entries(status=ConfigStatus.PENDING)
        processing = await self.storage.list_config_entries(status=ConfigStatus.APPLYING)
        failed = await self.storage.list_config_entries(status=ConfigStatus.FAILED)
        
        return {
            'network_status': self.network_monitor.status.value,
            'pending_count': len(pending),
            'processing_count': len(processing),
            'failed_count': len(failed),
            'last_network_check': self.network_monitor.last_check.isoformat() if self.network_monitor.last_check else None,
            'statistics': self.stats.copy()
        }
    
    async def cancel_config(self, entry_id: str) -> bool:
        """Cancel queued configuration.
        
        Args:
            entry_id: Configuration entry ID
            
        Returns:
            True if cancelled successfully
        """
        entry = await self.storage.get_config_entry(entry_id)
        if not entry:
            return False
        
        if entry.status in [ConfigStatus.APPLIED, ConfigStatus.SYNCED]:
            return False  # Cannot cancel completed configurations
        
        return await self.storage.delete_config_entry(entry_id)
    
    async def retry_failed_configs(self, device_id: Optional[str] = None) -> int:
        """Retry failed configurations.
        
        Args:
            device_id: Optional device filter
            
        Returns:
            Number of configurations queued for retry
        """
        failed_entries = await self.storage.list_config_entries(
            device_id=device_id,
            status=ConfigStatus.FAILED
        )
        
        retry_count = 0
        for entry in failed_entries:
            # Reset status and increment attempts
            entry.status = ConfigStatus.PENDING
            entry.attempts = 0  # Reset attempts for retry
            entry.updated_at = datetime.now()
            
            if await self.storage.save_config_entry(entry):
                await self.processing_queue.put(entry.entry_id)
                retry_count += 1
        
        logger.info(f"Queued {retry_count} failed configurations for retry")
        return retry_count
    
    async def _processing_loop(self):
        """Background processing loop."""
        while True:
            try:
                # Get next entry from queue
                entry_id = await self.processing_queue.get()
                
                # Process the configuration entry
                await self._process_config_entry(entry_id)
                
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Processing loop error: {e}")
                await asyncio.sleep(1)
    
    async def _process_config_entry(self, entry_id: str):
        """Process individual configuration entry."""
        try:
            entry = await self.storage.get_config_entry(entry_id)
            if not entry:
                return
            
            # Check if scheduled time has passed
            if entry.scheduled_at and entry.scheduled_at > datetime.now():
                # Re-queue for later
                await asyncio.sleep(1)
                await self.processing_queue.put(entry_id)
                return
            
            # Update status to processing
            entry.status = ConfigStatus.APPLYING
            entry.attempts += 1
            entry.last_attempt_at = datetime.now()
            entry.updated_at = datetime.now()
            await self.storage.save_config_entry(entry)
            
            # Apply configuration based on network status
            if self.network_monitor.status == NetworkStatus.ONLINE:
                success = await self._apply_config_online(entry)
            else:
                success = await self._apply_config_offline(entry)
            
            # Update entry status
            if success:
                entry.status = ConfigStatus.APPLIED
                self.stats['processed_configs'] += 1
            else:
                entry.status = ConfigStatus.FAILED
                self.stats['failed_configs'] += 1
            
            entry.updated_at = datetime.now()
            await self.storage.save_config_entry(entry)
            
        except Exception as e:
            logger.error(f"Error processing config entry {entry_id}: {e}")
            # Mark as failed
            entry = await self.storage.get_config_entry(entry_id)
            if entry:
                entry.status = ConfigStatus.FAILED
                entry.updated_at = datetime.now()
                await self.storage.save_config_entry(entry)
    
    async def _apply_config_online(self, entry: OfflineConfigEntry) -> bool:
        """Apply configuration when online."""
        try:
            # Get device info (would integrate with device registry)
            device_info = DeviceInfo(
                device_id=entry.device_id,
                name=f"Device {entry.device_id}",
                mac_address="00:00:00:00:00:00",
                signal_strength=-50,
                device_type="printer",
                firmware_version="1.0.0",
                last_seen=datetime.now()
            )
            
            # Apply configuration using batch manager
            if entry.template_id:
                # Apply template
                from .template_applicator import TemplateApplicator
                applicator = TemplateApplicator(self.template_manager, self.batch_manager)
                
                result = await applicator.apply_template_single(
                    entry.template_id, device_info, entry.config_data
                )
                return result.status.value == 'completed'
            else:
                # Direct configuration application
                # This would integrate with actual device communication
                await asyncio.sleep(0.1)  # Simulate application time
                return True
                
        except Exception as e:
            logger.error(f"Online config application error: {e}")
            return False
    
    async def _apply_config_offline(self, entry: OfflineConfigEntry) -> bool:
        """Apply configuration when offline (cache for later sync)."""
        try:
            # In offline mode, we just validate and cache the configuration
            # It will be applied when network becomes available
            
            # Mark as applied locally (will need sync later)
            entry.status = ConfigStatus.APPLIED
            return True
            
        except Exception as e:
            logger.error(f"Offline config application error: {e}")
            return False
    
    async def _sync_loop(self):
        """Background synchronization loop."""
        while True:
            try:
                await asyncio.sleep(self.sync_interval)
                
                # Only sync when online
                if self.network_monitor.status == NetworkStatus.ONLINE:
                    await self._perform_sync()
                
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Sync loop error: {e}")
    
    async def _perform_sync(self):
        """Perform synchronization with remote server."""
        try:
            # Find configurations that need syncing
            entries_to_sync = await self.storage.list_config_entries(
                status=ConfigStatus.APPLIED
            )
            
            if not entries_to_sync:
                return
            
            sync_op = SyncOperation(
                operation_id=str(uuid.uuid4()),
                operation_type="INCREMENTAL",
                device_ids=[e.device_id for e in entries_to_sync],
                start_time=datetime.now(),
                total_configs=len(entries_to_sync)
            )
            
            # Sync each configuration
            for entry in entries_to_sync:
                try:
                    # This would integrate with actual remote API
                    await asyncio.sleep(0.1)  # Simulate sync time
                    
                    # Mark as synced
                    entry.status = ConfigStatus.SYNCED
                    entry.updated_at = datetime.now()
                    await self.storage.save_config_entry(entry)
                    
                    sync_op.synced_configs += 1
                    
                except Exception as e:
                    sync_op.failed_configs += 1
                    sync_op.errors.append(f"Entry {entry.entry_id}: {str(e)}")
            
            sync_op.end_time = datetime.now()
            sync_op.status = "completed"
            
            self.stats['sync_operations'] += 1
            logger.info(f"Sync completed: {sync_op.synced_configs}/{sync_op.total_configs}")
            
        except Exception as e:
            logger.error(f"Sync operation error: {e}")
    
    def _on_network_status_change(self, old_status: NetworkStatus, new_status: NetworkStatus):
        """Handle network status changes."""
        logger.info(f"Network status changed: {old_status.value} -> {new_status.value}")
        
        if new_status == NetworkStatus.ONLINE and old_status != NetworkStatus.ONLINE:
            # Network came online - trigger sync
            asyncio.create_task(self._perform_sync())
    
    async def _cleanup_queue(self):
        """Clean up old entries from queue."""
        try:
            # Remove old entries beyond retention period
            cutoff_date = datetime.now() - timedelta(days=self.retention_days)
            
            all_entries = await self.storage.list_config_entries(limit=10000)
            cleanup_count = 0
            
            for entry in all_entries:
                if (entry.created_at < cutoff_date and 
                    entry.status in [ConfigStatus.SYNCED, ConfigStatus.FAILED]):
                    
                    if await self.storage.delete_config_entry(entry.entry_id):
                        cleanup_count += 1
            
            logger.info(f"Cleaned up {cleanup_count} old entries")
            
        except Exception as e:
            logger.error(f"Queue cleanup error: {e}")


# Example usage and testing
if __name__ == "__main__":
    import asyncio
    from unittest.mock import MagicMock
    
    async def test_offline_manager():
        """Test offline configuration manager."""
        # Create mock dependencies
        template_manager = MagicMock()
        batch_manager = MagicMock()
        
        # Configuration
        config = {
            'storage_path': '/tmp/test_offline',
            'max_queue_size': 100,
            'retention_days': 7,
            'sync_interval': 30,
            'network_monitor': {
                'check_interval': 10,
                'timeout': 5,
                'test_urls': ['https://httpbin.org/status/200']
            }
        }
        
        # Create manager
        manager = OfflineConfigManager(config, template_manager, batch_manager)
        
        try:
            # Start manager
            await manager.start()
            
            # Queue some configurations
            print("Queueing configurations...")
            
            config_ids = []
            for i in range(5):
                config_id = await manager.queue_config(
                    device_id=f"device_{i:03d}",
                    config_data={
                        "wifi": {
                            "ssid": f"Network_{i}",
                            "password": "password123"
                        }
                    },
                    operation_type=OperationType.UPDATE,
                    priority=5
                )
                config_ids.append(config_id)
                print(f"Queued config: {config_id}")
            
            # Check queue status
            await asyncio.sleep(2)
            status = await manager.get_queue_status()
            print(f"Queue status: {json.dumps(status, indent=2, default=str)}")
            
            # Wait for processing
            await asyncio.sleep(5)
            
            # Check final status
            final_status = await manager.get_queue_status()
            print(f"Final status: {json.dumps(final_status, indent=2, default=str)}")
            
        finally:
            await manager.stop()
    
    # Run test
    asyncio.run(test_offline_manager())